/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.security.core.spring.authentication;

import io.iec.edp.caf.session.holder.CafSecurityAPIKeyHolder;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 认证成功后事件（优先级最高），第一时间将Session持久化，解决计划任务和分布式锁中持久化前判断Session无效的问题
 */
@Slf4j
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {

    private SessionRepository sessionRepository;

    public AuthenticationSuccessListener(SessionRepository sessionRepository) {
        this.sessionRepository = sessionRepository;
    }

    @Override
    public void onApplicationEvent(AuthenticationSuccessEvent authenticationSuccessEvent) {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (servletRequestAttributes == null) {
            return;
        }
        HttpServletRequest request = servletRequestAttributes.getRequest();

        //API key场景
        setAPIKey(request);

        Authentication securityAuth = (Authentication) authenticationSuccessEvent.getSource();
        //创建一个空的，主要是避免对原有认证逻辑造成影响（SecurityContextHolder.getContext()）
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        if (securityAuth.isAuthenticated()) {
            //SecurityContextHolder.getContext().setAuthentication(securityAuth);
            context.setAuthentication(securityAuth);
        }
        Session session = this.getCurrentSession(request);
        if (session != null) {
            session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
            this.sessionRepository.save(session);
            //SecurityContextHolder.clearContext();
        }
    }

    /**
     * 获取当前Session
     *
     * @param request   HttpServletRequest
     * @return          Session
     */
    private Session getCurrentSession(HttpServletRequest request) {
        var httpSessionWrapper = request.getSession();
        if (httpSessionWrapper == null)
            return null;
        try {
            Method method = httpSessionWrapper.getClass().getSuperclass().getDeclaredMethod("getSession", (Class<?>[]) null);
            method.setAccessible(true);
            return (Session) method.invoke(httpSessionWrapper);
        } catch (Exception e) {
            throw new RuntimeException("调用目标错误", e);
        }
    }

    /**
     * 标识api key
     */
    private void setAPIKey(HttpServletRequest request) {
        Object attribute = request.getAttribute("caf-security-spring-authentication");
        boolean isApiKey = attribute != null && "io.iec.edp.caf.security.apikey.authentication.CafApiKeySpringAuthentication".equals(attribute.getClass().getName());

        CafSecurityAPIKeyHolder.set(isApiKey);
    }
}
